feat(merge): identity grouping — nest supersessions, split distinct tests (0046) - #13
Merged
siddhant573 merged 5 commits intoJul 20, 2026
Conversation
…split distinct tests) An optional tests.identity block in merge-rules.yaml turns a test-id collision from "pick one winner" into "group by identity". Same declared identity keeps the latest run in the canonical tests/<id>/ slot and nests superseded runs beneath it as 1/, 2/ …; different identity splits into a suffixed sibling folder. Realises the cross-environment matrix identity slice 0045 deferred. Purely additive — absent the block, merge behaves exactly as 0045 specifies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ests (0046) An optional tests.identity block turns a test-id collision from "pick one winner" into "group by identity". Copies whose declared keys all match keep the latest run in the canonical tests/<id>/ slot and archive superseded runs beneath it as 1/, 2/ … oldest first; copies that differ split into a suffixed sibling tests/<id>-1/. - collide: the union walk generalises from one incumbent per test id to an ordered list of groups, so a 3+-way collision is matched against every existing group before a new folder is minted. Single-member groups with folder === baseId are exactly 0045's behaviour — one code path, not two. - assemble: writes canonical + nested copies, rewrites `test` on split folders only (0031's directory equality), and pushes divergent environment down into every copy from its own source pack. - Guard rules still run first, so a must:same + error rule can never be downgraded into a split; discard tombstones the base id, siblings included. - Suffixes are reserved against every eligible pack's test ids, so a split never steals a name a pack legitimately owns. Nested copies are inert to totals, the failure index and validation, all of which scope themselves one level. Purely additive: absent the block, output is unchanged. 192 tests passing, build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"TMS test uuid" is product jargon in a decision whose point is that evidence-cli never interprets an identity key. Per 0002, nothing producer-specific belongs in the contract's vocabulary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The design viewer is generated from decision metadata, not prose: `governs`
is reverse-indexed into the "shaped by" chips on the spec page
(design/web/src/components/Contract.tsx builds tokens from it) and `feature`
attaches a decision to its spec-area node. 0046 shipped without either, so it
rendered in the decisions log but was invisible everywhere else — it was the
only one of 46 decisions missing them.
Adds src/design-links.test.ts to catch the class: every decision must declare
governs + feature, and every path-shaped governs entry must still exist. Note
a governs entry may be a conceptual AREA ("repo" in 0001/0025) rather than a
path, so only path-shaped entries are checked against the filesystem.
194 tests passing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also backfills the missing 0.1.6 CHANGELOG entry: that version is published on npm but was never documented here, which left a hole between 0.1.5 and this release. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An optional
tests.identityblock in merge-rules turns a test-id collision from "pick one winner" into "group by identity".tests/<id>/exactly as an uncontested test does; superseded runs are archived whole beneath it as1/,2/… oldest first.tests/<id>-1/,-2/.Purely additive. Absent the block, output is unchanged — there is a regression test asserting exactly that.
Why
Producers derive test ids (a slug plus a path hash), so merging two runs of the same suite collides on every test.
prefer_latestanswers that by discarding artifacts anddiscardby dropping the test outright — neither preserves history, and neither can tell "the same test, run again" apart from "two different tests that happen to share a derived name". This realises the slice 0045 deferred as "cross-environment matrix identity".Design notes
splitaction on the rule list. Grouping must ask "does this challenger belong to group 0, group 1, or neither?", which needs the whole key set as an up-front fact. Scraping it from "every rule whose action issplit" would make identity an emergent property of an unrelated ordering — adding a guard rule would silently redefine sameness.on_collisionvalues.error/prefer_first/prefer_latest/discardall answer which copy survives and never change the pack's shape.nest/splitanswer what shape the output takes.must: same+errorrule can never be downgraded into a split.discardtombstones the base id — split siblings included.<id>-1taken → allocates-2).tests/*, the failure index readstests/<id>/steps/, L1 checks enumerate<test>/steps/.keysare caller-supplied dot-paths; no producer field name enterssrc/or the schemas.Implementation
collide.tsgeneralises the union walk from one incumbent per test id to an ordered list of groups. A single-member group withfolder === baseIdis 0045's behaviour, so there is one code path rather than two.assemble.tswrites canonical + nested copies, rewritesteston split folders only (0031's directory equality), and pushes divergent environment down into every copy from its own source pack.Also in this PR
src/design-links.test.ts— 0046 initially shipped withoutgoverns/feature, which made it invisible to the design viewer everywhere except the decisions log; it was the only one of 46 decisions missing them. The new test catches the class. Note it revealed that agovernsentry may be a conceptual area (repoin 0001/0025) rather than a path, so only path-shaped entries are filesystem-checked.Verification
npx vitest run→ 194 passed / 20 filesnpm run build→ cleanContract.tsx'sdesign/contract/03-commands.mdtokenNote for consumers
on_same: nestretains artifacts thatprefer_latestdiscards today, so merged packs of re-runs grow with the number of runs kept. That is the intent of the feature, but it is a real change in output size for anyone who opts in.🤖 Generated with Claude Code